/*
 * Ext GWT 2.2.5 - Ext for GWT
 * Copyright(c) 2007-2010, Ext JS, LLC.
 * licensing@extjs.com
 * 
 * http://extjs.com/license
 */
package com.extjs.gxt.ui.client.widget;

import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style;
import com.extjs.gxt.ui.client.Style.Direction;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.core.DomHelper;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FxEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.fx.FxConfig;
import com.extjs.gxt.ui.client.util.Format;
import com.extjs.gxt.ui.client.util.IconHelper;
import com.extjs.gxt.ui.client.util.Markup;
import com.extjs.gxt.ui.client.util.Size;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.button.ButtonBar;
import com.extjs.gxt.ui.client.widget.button.ToolButton;
import com.extjs.gxt.ui.client.widget.layout.FlowLayout;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Accessibility;
import com.google.gwt.user.client.ui.Frame;

/**
 * ContentPanel is a component container that has specific functionality and
 * structural components that make it the perfect building block for
 * application-oriented user interfaces. The Panel contains bottom and top
 * toolbars, along with separate header, footer and body sections. It also
 * provides built-in expandable and collapsible behavior, along with a variety
 * of pre-built tool buttons that can be wired up to provide other customized
 * behavior.
 * 
 * Code snippet:
 * 
 * <pre>
 * ContentPanel cp = new ContentPanel();
 * cp.setHeading(&quot;Folder Contents&quot;);
 * cp.setSize(250, 140);
 * cp.setPosition(10, 10);
 * cp.setCollapsible(true);
 * cp.setFrame(true);
 * cp.setBodyStyle(&quot;backgroundColor: white;&quot;);
 * cp.getHeader().addTool(new ToolButton(&quot;x-tool-gear&quot;));
 * cp.getHeader().addTool(new ToolButton(&quot;x-tool-close&quot;));
 * cp.addText(getBogusText());
 * cp.addButton(new Button(&quot;Ok&quot;));
 * cp.setIconStyle(&quot;tree-folder-open&quot;);
 * RootPanel.get().add(cp);
 * cp.layout();
 * </pre>
 * 
 * <dl>
 * <dt><b>Events:</b></dt>
 * 
 * <dd><b>BeforeExpand</b> : ComponentEvent(component)<br>
 * <div>Fires before the panel is expanded. Listeners can cancel the action by
 * calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>component : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Expand</b> : ComponentEvent(component)<br>
 * <div>Fires after the panel is expanded</div>
 * <ul>
 * <li>component : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>BeforeCollapse</b> : ComponentEvent(component)<br>
 * <div>Fires before the panel is collapsed. Listeners can cancel the action by
 * calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>component : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Collapse</b> : ComponentEvent(component)<br>
 * <div>Fires after the panel is collapsed.</div>
 * <ul>
 * <li>component : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>BeforeClose</b> : ComponentEvent(component)<br>
 * <div>Fires before a content panel is closed. Listeners can cancel the action
 * by calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>component : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Close</b> : ComponentEvent(component)<br>
 * <div>Fires after a content panel is closed.</div>
 * <ul>
 * <li>component : this</li>
 * </ul>
 * </dd>
 * </dl>
 * 
 * <dl>
 * <dt>Inherited Events:</dt>
 * <dd>LayoutContainer AfterLayout</dt>
 * <dd>ScrollContainer Scroll</dd>
 * <dd>Container BeforeAdd</dd>
 * <dd>Container Add</dd>
 * <dd>Container BeforeRemove</dd>
 * <dd>Container Remove</dd>
 * <dd>BoxComponent Move</dd>
 * <dd>BoxComponent Resize</dd>
 * <dd>Component Enable</dd>
 * <dd>Component Disable</dd>
 * <dd>Component BeforeHide</dd>
 * <dd>Component Hide</dd>
 * <dd>Component BeforeShow</dd>
 * <dd>Component Show</dd>
 * <dd>Component Attach</dd>
 * <dd>Component Detach</dd>
 * <dd>Component BeforeRender</dd>
 * <dd>Component Render</dd>
 * <dd>Component BrowserEvent</dd>
 * <dd>Component BeforeStateRestore</dd>
 * <dd>Component StateRestore</dd>
 * <dd>Component BeforeStateSave</dd>
 * <dd>Component SaveState</dd>
 * </dl>
 */
public class ContentPanel extends LayoutContainer implements IconSupport {

	protected String bodStyle;
	protected El body, bwrap;
	protected String bwrapStyle;
	protected String collapseStyle;

	protected ButtonBar fbar;
	protected boolean frame;
	protected Header head;
	protected String headerStyle, footerStyle;
	protected String headerTextStyle;
	protected String tbarStyle, bbarStyle;
	private boolean animating;
	private boolean animCollapse = true;
	private boolean bodyBorder = true;
	private String bodyStyle, bodyStyleName;
	private Component bottomComponent;
	private HorizontalAlignment buttonAlign = HorizontalAlignment.END;
	private ToolButton collapseBtn;
	private boolean collapsed, hideCollapseTool;
	private boolean collapsible;
	private El foot, tbar, bbar;
	private boolean footer, titleCollapse;
	private boolean headerVisible = true;
	private int minButtonWidth = 75;

	private Component topComponent;

	/**
	 * Creates a new panel instance.
	 */
	public ContentPanel() {
		baseStyle = "x-panel";
		fbar = new ButtonBar();
		fbar.setParent(this);
		fbar.setMinButtonWidth(minButtonWidth);
		fbar.setAlignment(buttonAlign);
		head = new Header();
		head.setParent(this);
		disabledStyle = null;
		setDeferHeight(true);
		getFocusSupport().setIgnore(false);
	}

	/**
	 * Creates a new content panel.
	 * 
	 * @param layout the panel's layout
	 */
	public ContentPanel(Layout layout) {
		this();
		setLayout(layout);
	}

	/**
	 * Adds a button the the panel.
	 * 
	 * @param button the button to add
	 */
	public void addButton(Button button) {
		fbar.add(button);
	}

	/**
	 * Collapses the panel body so that it becomes hidden. Fires the
	 * <i>BeforeCollapse</i> before collapsing, then the <i>Collapse</i> event
	 * after collapsing.
	 */
	public void collapse() {
		if (rendered) {
			if (!collapsed && !animating && fireEvent(Events.BeforeCollapse)) {
				hideShadow();
				onCollapse();
			}
		} else {
			collapsed = true;
		}
	}

	/**
	 * Expands the panel body so that it becomes visible. Fires the
	 * <i>BeforeExpand</i> before expanding, then the <i>Expand</i> event after
	 * expanding.
	 */
	public void expand() {
		if (rendered) {
			if (collapsed && !animating && fireEvent(Events.BeforeExpand)) {
				hideShadow();
				onExpand();
			}
		} else {
			collapsed = false;
		}
	}

	/**
	 * Returns true if animation is enabled for expand / collapse.
	 * 
	 * @return the animCollapse true for animations
	 */
	public boolean getAnimCollapse() {
		return animCollapse;
	}

	/**
	 * Returns the panel's body element.
	 * 
	 * @return the body
	 */
	public El getBody() {
		return body;
	}

	/**
	 * Returns true if the body border is enabled.
	 * 
	 * @return the body border state
	 */
	public boolean getBodyBorder() {
		return bodyBorder;
	}

	/**
	 * Returns the body style.
	 * 
	 * @return the body style
	 */
	public String getBodyStyle() {
		return bodyStyle;
	}

	/**
	 * Returns the panel's bottom component.
	 * 
	 * @return the bottom component
	 */
	public Component getBottomComponent() {
		return bottomComponent;
	}

	/**
	 * Returns the panel's button alignment.
	 * 
	 * @return the button alignment
	 */
	public HorizontalAlignment getButtonAlign() {
		return buttonAlign;
	}

	public ButtonBar getButtonBar() {
		return fbar;
	}

	/**
	 * Returns the panel's collapse button.
	 * 
	 * @return the collapse button
	 */
	public ToolButton getCollapseBtn() {
		return collapseBtn;
	}

	/**
	 * Returns true if the panel is collapsible.
	 * 
	 * @return the collapsible state
	 */
	public boolean getCollapsible() {
		return collapsible;
	}

	/**
	 * Provides access to internal elements.
	 * <p>
	 * Valid values are:
	 * <ul>
	 * <li>"body"</li>
	 * <li>"header"</li>
	 * <li>"bwrap"</li>
	 * </ul>
	 * </p>
	 * 
	 * @param name the element name
	 * @return the element
	 */
	public Element getElement(String name) {
		if (name.equals("header")) {
			return head.getElement();
		} else if (name.equals("bwrap")) {
			return bwrap.dom;
		} else if (name.equals("body")) {
			return body.dom;
		}
		return null;
	}

	/**
	 * Returns true if framing is enabled.
	 * 
	 * @return the frame state
	 */
	public boolean getFrame() {
		return frame;
	}

	/**
	 * Returns the height in pixels of the framing elements of this panel
	 * (including any top and bottom bars and header and footer elements, but not
	 * including the body height). To retrieve the body height see
	 * {@link #getInnerHeight}.
	 * 
	 * @return the frame height
	 */
	public int getFrameHeight() {
		int h = el().getFrameWidth("tb") + bwrap.getFrameWidth("tb");
		if (frame) {
			Element hd = el().dom.getFirstChildElement().cast();
			Element ft = bwrap.dom.getLastChild().cast();
			h += (fly(hd).getHeight() + fly(ft).getHeight());
			Element mc = bwrap.subChild(3).dom;
			h += fly(mc).getFrameWidth("tb");
		} else {
			if (head != null) {
				h += head.getOffsetHeight();
			}
			if (foot != null) {
				h += foot.getHeight();
			}
		}
		h += (tbar != null ? tbar.getHeight() : 0) + (bbar != null ? bbar.getHeight() : 0);
		return h;
	}

	/**
	 * Returns the width in pixels of the framing elements of this panel (not
	 * including the body width). To retrieve the body width see
	 * {@link #getInnerWidth}.
	 * 
	 * @return The frame width
	 */
	public int getFrameWidth() {
		int w = el().getFrameWidth("lr") + bwrap.getFrameWidth("lr");
		if (frame) {
			Element l = bwrap.dom.getFirstChildElement().cast();
			w += (fly(l).getFrameWidth("l") + fly(l).firstChild().getFrameWidth("r"));
			Element mc = bwrap.subChild(3).dom;
			w += fly(mc).getFrameWidth("lr");
		}
		return w;
	}

	/**
	 * Returns the panel's header.
	 * 
	 * @return the header
	 */
	public Header getHeader() {
		return head;
	}

	/**
	 * Returns the panel's heading.
	 * 
	 * @return the heading
	 */
	public String getHeading() {
		return head.getText();
	}

	/**
	 * Returns the panel's icon style.
	 * 
	 * @return the icon style
	 */
	@Override
	public AbstractImagePrototype getIcon() {
		return head.getIcon();
	}

	/**
	 * Returns the height in pixels of the body element (not including the height
	 * of any framing elements). For the frame height see {@link #getFrameHeight}.
	 * 
	 * @return the inner height
	 */
	public int getInnerHeight() {
		return getHeight() - getFrameHeight();
	}

	/**
	 * Returns the width in pixels of the body element (not including the width of
	 * any framing elements). For the frame width see {@link #getFrameWidth}.
	 * 
	 * @return the body width
	 */
	public int getInnerWidth() {
		return getWidth() - getFrameWidth();
	}

	@Override
	public El getLayoutTarget() {
		return body;
	}

	public int getMinButtonWidth() {
		return minButtonWidth;
	}

	/**
	 * @return the titleCollapse
	 */
	public boolean getTitleCollapse() {
		return titleCollapse;
	}

	/**
	 * Returns the panel's title text.
	 * 
	 * @return the title text
	 * @deprecated use {@link #getHeading()}
	 */
	public String getTitleText() {
		return head.getText();
	}

	/**
	 * Returns the panels top component.
	 * 
	 * @return the top component
	 */
	public Component getTopComponent() {
		return topComponent;
	}

	/**
	 * Returns true if the panel is collapsed.
	 * 
	 * @return the collapsed state
	 */
	public boolean isCollapsed() {
		return collapsed;
	}

	/**
	 * Returns <code>true</code> if the panel is expanded.
	 * 
	 * @return the expand state
	 */
	public boolean isExpanded() {
		return !isCollapsed();
	}

	/**
	 * Returns true if the footer is enabled.
	 * 
	 * @return the show footer state
	 */
	public boolean isFooter() {
		return footer;
	}

	/**
	 * Returns true if the header is visible.
	 * 
	 * @return the header visible state
	 */
	public boolean isHeaderVisible() {
		return headerVisible;
	}

	/**
	 * Returns true if the collapse tool is hidden.
	 * 
	 * @return the hide collapse tool state
	 */
	public boolean isHideCollapseTool() {
		return hideCollapseTool;
	}

	@Override
	public void onComponentEvent(ComponentEvent ce) {
		super.onComponentEvent(ce);
		if (ce.getEventTypeInt() == Event.ONCLICK) {
			onClick(ce);
		}
	}

	/**
	 * Sets whether expand and collapse is animating (defaults to true,
	 * pre-render).
	 * 
	 * @param animCollapse true to enable animations
	 */
	public void setAnimCollapse(boolean animCollapse) {
		assertPreRender();
		this.animCollapse = animCollapse;
	}

	/**
	 * True to display the borders of the panel's body element, false to hide them
	 * (defaults to true, pre-render). Only applies to non-framed panels.
	 * 
	 * @param bodyBorder true for a body border
	 */
	public void setBodyBorder(boolean bodyBorder) {
		assertPreRender();
		this.bodyBorder = bodyBorder;
	}

	/**
	 * Custom CSS styles to be applied to the body element in the format expected
	 * by {@link El#applyStyles} (pre-render).
	 * 
	 * @param bodyStyle the body style
	 */
	public void setBodyStyle(String bodyStyle) {
		assertPreRender();
		this.bodyStyle = bodyStyle;
	}

	/**
	 * A style name that is added to the panel's body element (pre-render).
	 * 
	 * @param style the style name
	 */
	public void setBodyStyleName(String style) {
		assertPreRender();
		this.bodyStyleName = style;
	}

	/**
	 * Sets the panel's bottom component (pre-render). The component's natural
	 * height will be used and will not be changed by the panel.
	 * 
	 * @param bottomComponent the bottom component
	 */
	public void setBottomComponent(Component bottomComponent) {
		assertPreRender();
		if (this.bottomComponent != null) {
			Component c = this.bottomComponent;
			this.bottomComponent = null;
			c.removeFromParent();
		}
		if (bottomComponent != null) {
			this.bottomComponent = bottomComponent;
			this.bottomComponent.setParent(this);
		}
	}

	/**
	 * Sets the button alignment of any buttons added to this panel (defaults to
	 * RIGHT, pre-render).
	 * 
	 * @param buttonAlign the button alignment
	 */
	public void setButtonAlign(HorizontalAlignment buttonAlign) {
		assertPreRender();
		this.buttonAlign = Style.convertHorizontalAlignmentToRelative(buttonAlign);
		fbar.setAlignment(this.buttonAlign);
	}

	/**
	 * True to make the panel collapsible and have the expand/collapse toggle
	 * button automatically rendered into the header tool button area, false to
	 * keep the panel statically sized with no button (defaults to false,
	 * pre-render).
	 * 
	 * @param collapsible the collapsible to set
	 */
	public void setCollapsible(boolean collapsible) {
		assertPreRender();
		this.collapsible = collapsible;
	}

	/**
	 * Sets the panel's expand state.
	 * 
	 * @param expanded <code>true<code> true to expand
	 */
	public void setExpanded(boolean expanded) {
		if (expanded) {
			expand();
		} else {
			collapse();
		}
	}

	/**
	 * True to create the footer element explicitly, false to skip creating it
	 * (pre-render). By default, when footer is not specified, if one or more
	 * buttons have been added to the panel the footer will be created
	 * automatically, otherwise it will not.
	 * 
	 * @param footer the footer state
	 */
	public void setFooter(boolean footer) {
		assertPreRender();
		this.footer = footer;
	}

	/**
	 * True to render the panel with custom rounded borders, false to render with
	 * plain 1px square borders (defaults to false, pre-render).
	 * 
	 * @param frame true to use the frame style
	 */
	public void setFrame(boolean frame) {
		assertPreRender();
		this.frame = frame;
	}

	/**
	 * True to create the header element explicitly, false to skip creating it
	 * (defaults to true, pre-render). By default, when header is not specified,
	 * if a {@link #setHeading(String)} is set the header will be created
	 * automatically, otherwise it will not. If a title is set but header is
	 * explicitly set to false, the header will not be rendered.
	 * 
	 * @param headerVisible true to show the header
	 */
	public void setHeaderVisible(boolean headerVisible) {
		assertPreRender();
		this.headerVisible = headerVisible;
	}

	/**
	 * Sets the title text for the panel.
	 * 
	 * @param text the title text
	 */
	public void setHeading(String text) {
		head.setText(text);
	}

	/**
	 * Sets whether the collapse tool should be displayed when the panel is
	 * collapsible.
	 * 
	 * @param hideCollapseTool true if the tool is hidden
	 */
	public void setHideCollapseTool(boolean hideCollapseTool) {
		this.hideCollapseTool = hideCollapseTool;
		if (rendered) {
			collapseBtn.setVisible(!hideCollapseTool);
		}
	}

	/**
	 * Sets the item's icon style. The style name should match a CSS style that
	 * specifies a background image using the following format:
	 * 
	 * <pre>
	 * &lt;code&gt; .my-icon { background: url(images/icons/my-icon.png) no-repeat
	 * center left !important; } &lt;/code&gt;
	 * </pre>
	 * 
	 * @param icon the icon
	 */
	@Override
	public void setIcon(AbstractImagePrototype icon) {
		head.setIcon(icon);
	}

	@Override
	public void setIconStyle(String icon) {
		setIcon(IconHelper.create(icon));
	}

	/**
	 * True to display an interior border on the body element of the panel, false
	 * to hide it (defaults to true, pre-render). This only applies when
	 * {@link #setBodyBorder(boolean)} == true.
	 * 
	 * @param insetBorder true to display the interior border
	 * @deprecated behavior not implemented
	 */
	public void setInsetBorder(boolean insetBorder) {
		assertPreRender();
	}

	public void setMinButtonWidth(int minButtonWidth) {
		this.minButtonWidth = minButtonWidth;
		fbar.setMinButtonWidth(minButtonWidth);
	}

	/**
	 * True to allow expanding and collapsing the panel (when {@link #collapsible}
	 * = true) by clicking anywhere in the header bar, false to allow it only by
	 * clicking to tool button (defaults to false, pre-render).
	 * 
	 * @param titleCollapse the titleCollapse to set
	 */
	public void setTitleCollapse(boolean titleCollapse) {
		assertPreRender();
		this.titleCollapse = titleCollapse;
	}

	/**
	 * Sets the panel's top component (pre-render).
	 * 
	 * @param topComponent the component
	 */
	public void setTopComponent(Component topComponent) {
		assertPreRender();
		if (this.topComponent != null) {
			Component c = this.topComponent;
			this.topComponent = null;
			c.removeFromParent();
		}
		if (topComponent != null) {
			this.topComponent = topComponent;
			this.topComponent.setParent(this);
		}
	}

	/**
	 * Adds the content from the given url.
	 * 
	 * @param url the url
	 * @return the new frame instance
	 */
	public Frame setUrl(String url) {
		Frame f = new Frame(url);
		f.getElement().setPropertyInt("frameBorder", 0);
		f.setSize("100%", "100%");
		setLayout(new FlowLayout());
		removeAll();
		add(f);
		layout();
		return f;
	}

	protected Size adjustBodySize() {
		return new Size(0, 0);
	}

	protected void afterCollapse() {
		addStyleName(collapseStyle);
		removeStyleName(baseStyle + "-animated");
		collapsed = true;
		animating = false;

		for (Component c : getItems()) {
			if (!c.hidden && c.isRendered()) {
				c.notifyHide();
			}
		}

		if (topComponent != null && topComponent.isRendered()) {
			topComponent.notifyHide();
		}
		if (bottomComponent != null && bottomComponent.isRendered()) {
			bottomComponent.notifyHide();
		}
		if (fbar != null && fbar.isRendered()) {
			fbar.notifyHide();
		}

		sync(true);

		if (GXT.isAriaEnabled()) {
			Accessibility.setState(getElement(), "aria-expanded", "false");
			collapseBtn.setTitle(GXT.MESSAGES.panel_expandPanel());
		}

		ComponentEvent ce = new ComponentEvent(this);
		fireEvent(Events.Collapse, ce);
	}

	protected void afterExpand() {
		removeStyleName(collapseStyle);
		removeStyleName(baseStyle + "-animated");
		collapsed = false;
		animating = false;

		for (Component c : getItems()) {
			if (!c.hidden && c.isRendered()) {
				c.notifyShow();
			}
		}

		if (topComponent != null && topComponent.isRendered()) {
			topComponent.notifyShow();
		}
		if (bottomComponent != null && bottomComponent.isRendered()) {
			bottomComponent.notifyShow();
		}
		if (fbar != null && fbar.isRendered()) {
			fbar.notifyShow();
		}

		sync(true);

		if (GXT.isAriaEnabled()) {
			Accessibility.setState(getElement(), "aria-expanded", "true");
			collapseBtn.setTitle(GXT.MESSAGES.panel_collapsePanel());
		}

		ComponentEvent ce = new ComponentEvent(this);
		fireEvent(Events.Expand, ce);
	}

	@Override
	protected void createStyles(String baseStyle) {
		headerStyle = baseStyle + "-header";
		headerTextStyle = baseStyle + "-header-text";
		bwrapStyle = baseStyle + "-bwrap";
		tbarStyle = baseStyle + "-tbar";
		bodStyle = baseStyle + "-body";
		bbarStyle = baseStyle + "-bbar";
		footerStyle = baseStyle + "-footer";
		collapseStyle = baseStyle + "-collapsed";
	}

	@Override
	protected void doAttachChildren() {
		super.doAttachChildren();
		if (head.isRendered()) {
			ComponentHelper.doAttach(head);
		}
		if (fbar.isRendered()) {
			ComponentHelper.doAttach(fbar);
		}
		ComponentHelper.doAttach(topComponent);
		ComponentHelper.doAttach(bottomComponent);
	}

	@Override
	protected void doDetachChildren() {
		super.doDetachChildren();
		if (head.isRendered()) {
			ComponentHelper.doDetach(head);
		}
		ComponentHelper.doDetach(fbar);
		ComponentHelper.doDetach(topComponent);
		ComponentHelper.doDetach(bottomComponent);
	}

	// internal, used for performance reasons
	protected Size getFrameSize() {
		Size elFrameSize = el().getFrameSize();
		Size bwrapFrameSize = bwrap.getFrameSize();

		Size mcFrameSize = null;
		if (frame) {
			Element mc = bwrap.subChild(3).dom;
			mcFrameSize = fly(mc).getFrameSize();
		}

		int w = elFrameSize.width + bwrapFrameSize.width;
		if (frame) {
			Element l = bwrap.dom.getFirstChildElement().cast();
			w += (fly(l).getFrameWidth("l") + fly(l).firstChild().getFrameWidth("r"));
			w += mcFrameSize.width;
		}

		int h = elFrameSize.height + bwrapFrameSize.height;
		if (frame) {
			Element hd = el().dom.getFirstChildElement().cast();
			Element ft = bwrap.dom.getLastChild().cast();
			h += (fly(hd).getHeight() + fly(ft).getHeight());
			h += mcFrameSize.height;
		} else {
			if (head != null) {
				h += head.getOffsetHeight();
			}
			if (foot != null) {
				h += foot.getHeight();
			}
		}
		h += (tbar != null ? tbar.getHeight() : 0) + (bbar != null ? bbar.getHeight() : 0);

		return new Size(w, h);
	}

	protected void initTools() {
		if (collapsible && !hideCollapseTool) {
			collapseBtn = new ToolButton("x-tool-toggle");
			if (GXT.isAriaEnabled()) {
				collapseBtn.setTitle(GXT.MESSAGES.panel_collapsePanel());
			}
			collapseBtn.addListener(Events.Select, new Listener<ComponentEvent>() {
				@Override
				public void handleEvent(ComponentEvent ce) {
					ce.stopEvent();
					setExpanded(!isExpanded());
				}
			});
			head.addTool(collapseBtn);
		}
	}

	protected void layoutBars() {
		if (footer && !fbar.layoutExecuted) {
			fbar.layout(false);
		}
		if (topComponent instanceof Container<?> && !((Container<?>) topComponent).layoutExecuted) {
			((Container<?>) topComponent).layout(false);
		}
		if (bottomComponent instanceof Container<?> && !((Container<?>) bottomComponent).layoutExecuted) {
			((Container<?>) bottomComponent).layout(false);
		}
	}

	@Override
	protected void notifyHide() {
		if (!collapsed) {
			super.notifyHide();

			if (topComponent != null && topComponent.isRendered()) {
				topComponent.notifyHide();
			}
			if (bottomComponent != null && bottomComponent.isRendered()) {
				bottomComponent.notifyHide();
			}
			if (fbar != null && fbar.isRendered()) {
				fbar.notifyHide();
			}
		}
		if (headerVisible && head != null) {
			head.notifyHide();
		}
	}

	@Override
	protected void notifyShow() {
		if (!collapsed) {
			super.notifyShow();

			if (topComponent != null && topComponent.isRendered()) {
				topComponent.notifyShow();
			}
			if (bottomComponent != null && bottomComponent.isRendered()) {
				bottomComponent.notifyShow();
			}
			if (fbar != null && fbar.isRendered()) {
				fbar.notifyShow();
			}
		}
		if (headerVisible && head != null) {
			head.notifyShow();
		}
	}

	protected void onClick(ComponentEvent ce) {
		if (collapsible && titleCollapse && head != null && ce.within(head.getElement())) {
			setExpanded(!isExpanded());
		}
	}

	protected void onCollapse() {
		if (animCollapse) {
			animating = true;
			addStyleName(baseStyle + "-animated");
			bwrap.slideOut(Direction.UP, new FxConfig(300, new Listener<FxEvent>() {
				@Override
				public void handleEvent(FxEvent fe) {
					afterCollapse();
				}
			}));
		} else {
			bwrap.hide();
			afterCollapse();
		}
	}

	@Override
	protected void onDisable() {
		mask();
		super.onDisable();
	}

	@Override
	protected void onEnable() {
		unmask();
		super.onEnable();
	}

	protected void onExpand() {
		if (animCollapse) {
			animating = true;
			addStyleName(baseStyle + "-animated");
			bwrap.slideIn(Direction.DOWN, new FxConfig(300, new Listener<FxEvent>() {
				@Override
				public void handleEvent(FxEvent fe) {
					afterExpand();
				}
			}));
		} else {
			bwrap.show();
			afterExpand();
		}
	}

	@Override
	protected void onRender(Element parent, int pos) {
		super.onRender(parent, pos);

		if (fbar.getItemCount() > 0) {
			footer = true;
		}

		if (frame) {
			String s = Format.substitute(Markup.BBOX, baseStyle);
			DomHelper.insertHtml("afterBegin", el().dom, s);

			head.baseStyle = headerStyle;
			head.setTextStyle(headerTextStyle);
			initTools();
			head.render(el().dom);
			el().subChild(3).dom.appendChild(head.getElement());
			bwrap = el().createChild("<div role=presentation class='" + bwrapStyle + "'></div>");

			Element bw = bwrap.dom;
			Element ml = DOM.getChild(el().dom, 1);
			Element bl = DOM.getChild(el().dom, 2);
			DOM.appendChild(bw, ml);
			DOM.appendChild(bw, bl);

			Element mc = fly(bw).getSubChild(3);

			if (topComponent != null) {
				tbar = fly(mc).createChild("<div role=presentation class=" + tbarStyle + "></div>");
			}
			body = fly(mc).createChild("<div role=presentation class=" + bodStyle + "></div>");
			if (bottomComponent != null) {
				bbar = fly(mc).createChild("<div role=presentation class=" + bbarStyle + "></div>");
			}

			El e = fly(bw).lastChild().firstChild().firstChild();
			foot = e.createChild("<div role=presentation class=" + footerStyle + "></div>");

		} else {
			head.baseStyle = headerStyle;
			head.setTextStyle(headerTextStyle);
			initTools();
			head.render(el().dom);
			bwrap = el().createChild("<div role=presentation class=" + bwrapStyle + "></div>");

			Element bw = bwrap.dom;
			if (topComponent != null) {
				tbar = fly(bw).createChild("<div role=presentation class=" + tbarStyle + "></div>");
			}
			body = fly(bw).createChild("<div role=presentation class=" + bodStyle + "></div>");
			if (bottomComponent != null) {
				bbar = fly(bw).createChild("<div role=presentation class=" + bbarStyle + "></div>");
			}
			foot = fly(bw).createChild("<div role=presentation class=" + footerStyle + "></div>");
		}

		if (!headerVisible) {
			head.hide();
			body.addStyleName(bodStyle + "-noheader");
			if (tbar != null) {
				tbar.addStyleName(tbarStyle + "-noheader");
			}
		}

		if (footer && fbar.getItemCount() > 0) {
			Element div = DOM.createDiv();
			fly(div).addStyleName("x-panel-btns");
			foot.appendChild(div);
			fbar.render(div);
			Element clearDiv = DOM.createDiv();
			clearDiv.setClassName("x-clear");
			div.appendChild(clearDiv);
		} else if (!footer) {
			bwrap.lastChild().addStyleName(baseStyle + "-nofooter");
		}

		if (!bodyBorder) {
			el().addStyleName(baseStyle + "-noborder");
			body.addStyleName(bodStyle + "-noborder");
			if (tbar != null) {
				tbar.addStyleName(tbarStyle + "-noborder");
			}
			if (bbar != null) {
				bbar.addStyleName(bbarStyle + "-noborder");
			}
		}

		if (bodyStyle != null) {
			body.applyStyles(bodyStyle);
		}

		if (bodyStyleName != null) {
			body.addStyleName(bodyStyleName);
		}

		if (headerVisible) {
			head.disableTextSelection(true);
		}

		if (topComponent != null) {
			topComponent.render(tbar.dom);
		}

		if (bottomComponent != null) {
			bottomComponent.render(bbar.dom);
		}

		if (titleCollapse) {
			head.setStyleAttribute("cursor", "pointer");
			sinkEvents(Event.ONCLICK);
		}

		if (collapsible && GXT.isAriaEnabled()) {
			Accessibility.setState(getElement(), "aria-expanded", "true");
		}

		if (GXT.isAriaEnabled()) {
			Accessibility.setRole(getElement(), "region");
			if (head != null) {
				getAriaSupport().setLabelledBy(head.getId() + "-label");
			}
		}

		// early render
		layoutBars();

		if (collapsed) {
			boolean anim = animCollapse;
			collapsed = false;
			setAnimCollapse(false);
			collapse();
			setAnimCollapse(anim);
		}
	}

	@Override
	protected void onResize(int width, int height) {
		super.onResize(width, height);
		Size frameSize = getFrameSize();
		Size adjustBodySize = adjustBodySize();
		if (isAutoWidth()) {
			getLayoutTarget().setWidth("auto");
		} else if (width != -1) {
			width -= frameSize.width;
			if (tbar != null) {
				tbar.setWidth(width, true);
				if (topComponent instanceof BoxComponent) {
					((BoxComponent) topComponent).setWidth(width);
				}
			}
			if (bbar != null) {
				bbar.setWidth(width, true);
				if (bottomComponent instanceof BoxComponent) {
					((BoxComponent) bottomComponent).setWidth(width);
				}
			}
			if (fbar.isRendered()) {
				fbar.setWidth(width - fbar.el().getParent().getFrameWidth("lr"));
			}
			getLayoutTarget().setWidth(width - adjustBodySize.width, true);
		}
		if (isAutoHeight()) {
			getLayoutTarget().setHeight("auto");
		} else if (height != -1) {
			height -= frameSize.height;
			getLayoutTarget().setHeight(height - adjustBodySize.height, true);
		}
		if (mask) {
			mask(maskMessage, maskMessageStyleName);
		}
	}

	@Override
	protected boolean remove(Component item) {
		if (item == topComponent) {
			setTopComponent(null);
			return true;
		} else if (item == bottomComponent) {
			setBottomComponent(null);
			return true;
		}
		return super.remove(item);
	}

}